package com.mayikt.spike.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.util.concurrent.RateLimiter;
import com.mayikt.base.BaseApiService;
import com.mayikt.base.BaseResponse;
import com.mayikt.base.enums.ApiResponseEnum;
import com.mayikt.core.util.GenerateTokenUtils;
import com.mayikt.core.util.RedisUtils;
import com.mayikt.exception.BizException;
import com.mayikt.spike.mapper.OrderMapper;
import com.mayikt.spike.mapper.SeckillMapper;
import com.mayikt.spike.mapper.entity.Seckill;
import com.mayikt.spike.produce.SpikeCommodityProducer;
import com.mayikt.spike.service.SpikeCommodityService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSONObject;

import lombok.extern.slf4j.Slf4j;
import springfox.documentation.spring.web.json.Json;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.LongStream;

@RestController
@Slf4j
@Service
public class SpikeCommodityServiceImpl extends BaseApiService<JSONObject> implements SpikeCommodityService {
	@Autowired
	private SeckillMapper seckillMapper;
	@Autowired
	private OrderMapper orderMapper;
	@Autowired
	private RedisUtils redisUtils;
	@Autowired
	private GenerateTokenUtils generateTokenUtils;
    @Autowired
    private SpikeCommodityProducer spikeCommodityProducer;
    // 每秒时间存入令牌桶的token数为1个 1s/1r
    private RateLimiter rateLimiter = RateLimiter.create(100);
	private static final String SECKILL_TOKEN_PRE = "product_";


    private BaseResponse<JSONObject> spikeFallback(String phone, Long seckillId){
        log.info("服务器繁忙，请稍后再试！");
        return setResultError("服务器繁忙，请稍后再试！");
    }

	@Override
	@Transactional
    @HystrixCommand(fallbackMethod = "spikeFallback")
	public BaseResponse<JSONObject> spike(String phone, Long seckillId) {
        // 阻塞超时时间为 0
//        boolean tryAcquire = rateLimiter.tryAcquire(0, TimeUnit.SECONDS);
//        if (!tryAcquire){
//            return setResultError("当前排队人数过多，请稍后再试...");
//        }
		// 1.参数验证
		if (StringUtils.isEmpty(phone)) {
			return setResultError("手机号码不能为空!");
		}
		if (seckillId == null) {
			return setResultError("商品库存id不能为空!");
		}
		Seckill seckillEntity = seckillMapper.findBySeckillId(seckillId);
		if (seckillEntity == null) {
			return setResultError("商品信息不存在!");
		}
		// 2.用户频率限制 setnx 如果key存在话

//		Boolean reusltNx = redisUtils.setNx(phone, seckillId + "", 10l);
//		if (!reusltNx) {
//			return setResultError("访问次数过多，10秒后在实现重试!");
//		}


        //>>>>>>>>>>>>>>>>>>>>>>>>版本1 ，使用数据库乐观锁实现<<<<<<<<<<<<<<<<<<<<<<<<<<<<//
		// 3.修改数据库对应的库存 1万中只有100个抢购成功 提前生成好100个token 谁能够抢购成功token放入到mq中实现异步修改库存
//		Long version = seckillEntity.getVersion();
//		int updateRow = seckillMapper.inventoryDeduction(seckillId, version);
//		if (updateRow == 0) {
//			log.info(">>>修改库存失败>>>>inventoryDeduction返回为{} 秒杀失败！", JSONObject.toJSONString(updateRow));
//			return setResultError("亲，请稍后重试!");
//		}
//        Order order = new Order();
//        order.setUserPhone(phone);
//        order.setSeckillId(seckillId);
//        order.setState(1);
//        order.setCreateTime(LocalDateTime.now());
//        int insertRow = orderMapper.insert(order);
//        if (insertRow <= 0) {
//            return setResultError("亲，请稍后重试!");
//        }
//        log.info(">>>修改库存成功>>>>inventoryDeduction返回为{} 秒杀成功", insertRow);
//        return setResultSuccess("恭喜您，秒杀成功!");
        //>>>>>>>>>>>>>>>>>>>>>>>>版本1 ，使用数据库乐观锁实现<<<<<<<<<<<<<<<<<<<<<<<<<<<<//

        //>>>>>>>>>>>>>>>>>>>>>>>版本2 ，使用redis 令牌桶加mq实现<<<<<<<<<<<<<<<<<<<<<<<<<<//
        String productToken = generateTokenUtils.getListKeyToken(SECKILL_TOKEN_PRE + seckillId);
		if (StrUtil.isEmpty(productToken)){
		    return setResult(ApiResponseEnum.FAIL.getCode(),"商品已经售完，请下次再来！",null);
        }
        // 4.添加秒杀成功订单 基于MQ实现异步形式
        sendSeckillMsg(seckillId, phone, productToken);
        return setResultSuccess("正在排队中.......");
        //>>>>>>>>>>>>>>>>>>>>>>>版本2 ，使用redis 令牌桶加mq实现<<<<<<<<<<<<<<<<<<<<<<<<<<//
	}

    /**
     * 获取到秒杀token之后，异步放入mq中实现修改商品的库存
     */
    @Async
    public void sendSeckillMsg(Long seckillId, String phone,String productToken) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("seckillId", seckillId);
        jsonObject.put("phone", phone);
        jsonObject.put("productToken", productToken);
        spikeCommodityProducer.sendMsg(jsonObject);
    }

    /**
     * 新增对应商品库存令牌桶
     *
     * @param seckillId
     * @param tokenNum
     * @return
     */
    @Override
    public BaseResponse<JSONObject> addSpikeToken(Long seckillId, Long tokenNum) {
        if (BeanUtil.isEmpty(seckillId)){
            log.info("秒杀商品id不能为空！");
            throw new BizException(ApiResponseEnum.PARAMETER_CAN_NOT_NULL,"秒杀商品id不能为空");
        }
        if (BeanUtil.isEmpty(tokenNum)){
            log.info("秒杀商品数量不能为空！");
            throw new BizException(ApiResponseEnum.PARAMETER_CAN_NOT_NULL,"秒杀商品数量不能为空");
        }
        List<String> tokenList = new ArrayList<>();
        LongStream.range(0,tokenNum).forEach(i->redisUtils.lSet(SECKILL_TOKEN_PRE+seckillId,UUID.randomUUID().toString()));
        return setResultSuccess();
    }
}
